#include "wx/wxprec.h"
#include "wx/menu.h"
#include "wx/accel.h"
#include "wx/stockitem.h"
#include "wx/gtk/private.h"
#include <gdk/gdktypes.h>


#define gtk_accel_group_attach(g, o) gtk_window_add_accel_group((o), (g))
#define ACCEL_OBJECT        GtkWindow
#define ACCEL_OBJECTS(a)    (a)->acceleratables
#define ACCEL_OBJ_CAST(obj) ((GtkWindow*) obj)

static const int wxGTK_TITLE_ID = -3;
static wxString GetGtkHotKey( const wxMenuItem& item );

static wxString wxReplaceUnderscore( const wxString& title ) {
  const wxChar *pc;
  wxString str;
  pc = title;
  while( *pc != wxT( '\0' ) ) {
    if( ( *pc == wxT( '&' ) ) && ( *( pc + 1 ) == wxT( '&' ) ) ) {
      ++pc;
      str << wxT( '&' );
    } else if( *pc == wxT( '&' ) ) {
      str << wxT( '_' );
    } else {
      if( *pc == wxT( '_' ) ) {
        str << *pc;
      }
      str << *pc;
    }
    ++pc;
  }
  return str;
}

static wxString wxConvertFromGTKToWXLabel( const wxString& gtkLabel ) {
  wxString label;
  for( const wxChar *pc = gtkLabel.c_str(); *pc; pc++ ) {
    if( *pc == wxT( '_' ) && *( pc + 1 ) == wxT( '_' ) ) {
      label += wxT( '_' );
      pc++;
    } else if( *pc == wxT( '_' ) ) {
      label += wxT( '&' );
    } else if( *pc == wxT( '&' ) ) {
      label += wxT( "&&" );
    } else {
      label += *pc;
    }
  }
  return label;
}

static void DoCommonMenuCallbackCode( wxMenu *menu, wxMenuEvent& event ) {
  if( g_isIdle ) {
    wxapp_install_idle_handler();
  }
  event.SetEventObject( menu );
  wxEvtHandler* handler = menu->GetEventHandler();
  if( handler && handler->ProcessEvent( event ) ) {
    return;
  }
  wxWindow *win = menu->GetInvokingWindow();
  if( win ) {
    win->GetEventHandler()->ProcessEvent( event );
  }
}

extern "C" {

  static void gtk_menu_open_callback( GtkWidget *widget, wxMenu *menu ) {
    wxMenuEvent event( wxEVT_MENU_OPEN, -1, menu );
    DoCommonMenuCallbackCode( menu, event );
  }

  static void gtk_menu_close_callback( GtkWidget *widget, wxMenuBar *menubar ) {
    if( !menubar->GetMenuCount() ) {
      return;
    }
    wxMenuEvent event( wxEVT_MENU_CLOSE, -1, NULL );
    DoCommonMenuCallbackCode( menubar->GetMenu( 0 ), event );
  }

}

IMPLEMENT_DYNAMIC_CLASS( wxMenuBar, wxWindow )

void wxMenuBar::Init( size_t n, wxMenu *menus[], const wxString titles[], long style ) {
  m_needParent = false;
  m_style = style;
  m_invokingWindow = ( wxWindow* ) NULL;
  if( !PreCreation( ( wxWindow* ) NULL, wxDefaultPosition, wxDefaultSize ) ||
      !CreateBase( ( wxWindow* ) NULL, -1, wxDefaultPosition, wxDefaultSize, style, wxDefaultValidator, wxT( "menubar" ) ) ) {
    wxFAIL_MSG( wxT( "wxMenuBar creation failed" ) );
    return;
  }
  m_menubar = gtk_menu_bar_new();
  if( style & wxMB_DOCKABLE ) {
    m_widget = gtk_handle_box_new();
    gtk_container_add( GTK_CONTAINER( m_widget ), GTK_WIDGET( m_menubar ) );
    gtk_widget_show( GTK_WIDGET( m_menubar ) );
  } else {
    m_widget = GTK_WIDGET( m_menubar );
  }
  PostCreation();
  ApplyWidgetStyle();
  for( size_t i = 0; i < n; ++i ) {
    Append( menus[i], titles[i] );
  }
  g_signal_connect( m_menubar, "deactivate", G_CALLBACK( gtk_menu_close_callback ), this );
}

wxMenuBar::wxMenuBar( size_t n, wxMenu *menus[], const wxString titles[], long style ) {
  Init( n, menus, titles, style );
}

wxMenuBar::wxMenuBar( long style ) {
  Init( 0, NULL, NULL, style );
}

wxMenuBar::wxMenuBar() {
  Init( 0, NULL, NULL, 0 );
}

wxMenuBar::~wxMenuBar() {
}

static void wxMenubarUnsetInvokingWindow( wxMenu *menu, wxWindow *win ) {
  menu->SetInvokingWindow( ( wxWindow* ) NULL );
  wxWindow *top_frame = win;
  while( top_frame->GetParent() && !( top_frame->IsTopLevel() ) ) {
    top_frame = top_frame->GetParent();
  }
  wxMenuItemList::compatibility_iterator node = menu->GetMenuItems().GetFirst();
  while( node ) {
    wxMenuItem *menuitem = node->GetData();
    if( menuitem->IsSubMenu() ) {
      wxMenubarUnsetInvokingWindow( menuitem->GetSubMenu(), win );
    }
    node = node->GetNext();
  }
}

static void wxMenubarSetInvokingWindow( wxMenu *menu, wxWindow *win ) {
  menu->SetInvokingWindow( win );
  wxWindow *top_frame = win;
  while( top_frame->GetParent() && !( top_frame->IsTopLevel() ) ) {
    top_frame = top_frame->GetParent();
  }
  ACCEL_OBJECT *obj = ACCEL_OBJ_CAST( top_frame->m_widget );
  if( !g_slist_find( ACCEL_OBJECTS( menu->m_accel ), obj ) ) {
    gtk_accel_group_attach( menu->m_accel, obj );
  }
  wxMenuItemList::compatibility_iterator node = menu->GetMenuItems().GetFirst();
  while( node ) {
    wxMenuItem *menuitem = node->GetData();
    if( menuitem->IsSubMenu() ) {
      wxMenubarSetInvokingWindow( menuitem->GetSubMenu(), win );
    }
    node = node->GetNext();
  }
}

void wxMenuBar::SetInvokingWindow( wxWindow *win ) {
  m_invokingWindow = win;
  wxWindow *top_frame = win;
  while( top_frame->GetParent() && !( top_frame->IsTopLevel() ) ) {
    top_frame = top_frame->GetParent();
  }
  wxMenuList::compatibility_iterator node = m_menus.GetFirst();
  while( node ) {
    wxMenu *menu = node->GetData();
    wxMenubarSetInvokingWindow( menu, win );
    node = node->GetNext();
  }
}

void wxMenuBar::SetLayoutDirection( wxLayoutDirection dir ) {
  if( dir == wxLayout_Default ) {
    const wxWindow *const frame = GetFrame();
    if( frame ) {
      dir = frame->GetLayoutDirection();
    } else {
      dir = wxTheApp->GetLayoutDirection();
    }
  }
  if( dir == wxLayout_Default ) {
    return;
  }
  GTKSetLayout( m_menubar, dir );
  for( wxMenuList::compatibility_iterator node = m_menus.GetFirst(); node; node = node->GetNext() ) {
    wxMenu *const menu = node->GetData();
    menu->SetLayoutDirection( dir );
  }
}

wxLayoutDirection wxMenuBar::GetLayoutDirection() const {
  return GTKGetLayout( m_menubar );
}

void wxMenuBar::Attach( wxFrame *frame ) {
  wxMenuBarBase::Attach( frame );
  SetLayoutDirection( wxLayout_Default );
}

void wxMenuBar::UnsetInvokingWindow( wxWindow *win ) {
  m_invokingWindow = ( wxWindow* ) NULL;
  wxWindow *top_frame = win;
  while( top_frame->GetParent() && !( top_frame->IsTopLevel() ) ) {
    top_frame = top_frame->GetParent();
  }
  wxMenuList::compatibility_iterator node = m_menus.GetFirst();
  while( node ) {
    wxMenu *menu = node->GetData();
    wxMenubarUnsetInvokingWindow( menu, win );
    node = node->GetNext();
  }
}

bool wxMenuBar::Append( wxMenu *menu, const wxString &title ) {
  if( !wxMenuBarBase::Append( menu, title ) ) {
    return false;
  }
  return GtkAppend( menu, title );
}

bool wxMenuBar::GtkAppend( wxMenu *menu, const wxString& title, int pos ) {
  wxString str( wxReplaceUnderscore( title ) );
  menu->SetTitle( str );
  menu->m_owner = gtk_menu_item_new_with_mnemonic( wxGTK_CONV( str ) );
  menu->SetLayoutDirection( GetLayoutDirection() );
  gtk_widget_show( menu->m_owner );
  gtk_menu_item_set_submenu( GTK_MENU_ITEM( menu->m_owner ), menu->m_menu );
  if( pos == -1 ) {
    gtk_menu_shell_append( GTK_MENU_SHELL( m_menubar ), menu->m_owner );
  } else {
    gtk_menu_shell_insert( GTK_MENU_SHELL( m_menubar ), menu->m_owner, pos );
  }
  g_signal_connect( menu->m_owner, "activate", G_CALLBACK( gtk_menu_open_callback ), menu );
  if( m_invokingWindow ) {
    wxMenubarSetInvokingWindow( menu, m_invokingWindow );
    wxFrame *frame = wxDynamicCast( m_invokingWindow, wxFrame );
    if( frame ) {
      frame->UpdateMenuBarSize();
    }
  }
  return true;
}

bool wxMenuBar::Insert( size_t pos, wxMenu *menu, const wxString& title ) {
  if( !wxMenuBarBase::Insert( pos, menu, title ) ) {
    return false;
  }
  if( !GtkAppend( menu, title, ( int )pos ) ) {
    return false;
  }
  return true;
}

wxMenu *wxMenuBar::Replace( size_t pos, wxMenu *menu, const wxString& title ) {
  wxMenu *menuOld = Remove( pos );
  if( menuOld && !Insert( pos, menu, title ) ) {
    return ( wxMenu* ) NULL;
  }
  return menuOld;
}

wxMenu *wxMenuBar::Remove( size_t pos ) {
  wxMenu *menu = wxMenuBarBase::Remove( pos );
  if( !menu ) {
    return ( wxMenu* ) NULL;
  }
  gtk_menu_item_remove_submenu( GTK_MENU_ITEM( menu->m_owner ) );
  gtk_container_remove( GTK_CONTAINER( m_menubar ), menu->m_owner );
  gtk_widget_destroy( menu->m_owner );
  menu->m_owner = NULL;
  if( m_invokingWindow ) {
    wxFrame *frame = wxDynamicCast( m_invokingWindow, wxFrame );
    if( frame ) {
      frame->UpdateMenuBarSize();
    }
  }
  return menu;
}

static int FindMenuItemRecursive( const wxMenu *menu, const wxString &menuString, const wxString &itemString ) {
  if( wxMenuItem::GetLabelFromText( wxConvertFromGTKToWXLabel( menu->GetTitle() ) ) == wxMenuItem::GetLabelFromText( menuString ) ) {
    int res = menu->FindItem( itemString );
    if( res != wxNOT_FOUND ) {
      return res;
    }
  }
  wxMenuItemList::compatibility_iterator node = menu->GetMenuItems().GetFirst();
  while( node ) {
    wxMenuItem *item = node->GetData();
    if( item->IsSubMenu() ) {
      return FindMenuItemRecursive( item->GetSubMenu(), menuString, itemString );
    }
    node = node->GetNext();
  }
  return wxNOT_FOUND;
}

int wxMenuBar::FindMenuItem( const wxString &menuString, const wxString &itemString ) const {
  wxMenuList::compatibility_iterator node = m_menus.GetFirst();
  while( node ) {
    wxMenu *menu = node->GetData();
    int res = FindMenuItemRecursive( menu, menuString, itemString );
    if( res != -1 ) {
      return res;
    }
    node = node->GetNext();
  }
  return wxNOT_FOUND;
}

static wxMenuItem* FindMenuItemByIdRecursive( const wxMenu* menu, int id ) {
  wxMenuItem* result = menu->FindChildItem( id );
  wxMenuItemList::compatibility_iterator node = menu->GetMenuItems().GetFirst();
  while( node && result == NULL ) {
    wxMenuItem *item = node->GetData();
    if( item->IsSubMenu() ) {
      result = FindMenuItemByIdRecursive( item->GetSubMenu(), id );
    }
    node = node->GetNext();
  }
  return result;
}

wxMenuItem* wxMenuBar::FindItem( int id, wxMenu **menuForItem ) const {
  wxMenuItem* result = 0;
  wxMenuList::compatibility_iterator node = m_menus.GetFirst();
  while( node && result == 0 ) {
    wxMenu *menu = node->GetData();
    result = FindMenuItemByIdRecursive( menu, id );
    node = node->GetNext();
  }
  if( menuForItem ) {
    *menuForItem = result ? result->GetMenu() : ( wxMenu * )NULL;
  }
  return result;
}

void wxMenuBar::EnableTop( size_t pos, bool flag ) {
  wxMenuList::compatibility_iterator node = m_menus.Item( pos );
  wxCHECK_RET( node, wxT( "menu not found" ) );
  wxMenu* menu = node->GetData();
  if( menu->m_owner ) {
    gtk_widget_set_sensitive( menu->m_owner, flag );
  }
}

wxString wxMenuBar::GetLabelTop( size_t pos ) const {
  wxMenuList::compatibility_iterator node = m_menus.Item( pos );
  wxCHECK_MSG( node, wxT( "invalid" ), wxT( "menu not found" ) );
  wxMenu* menu = node->GetData();
  return wxStripMenuCodes( wxConvertFromGTKToWXLabel( menu->GetTitle() ) );
}

wxString wxMenuBar::GetMenuLabel( size_t pos ) const {
  wxMenuList::compatibility_iterator node = m_menus.Item( pos );
  wxCHECK_MSG( node, wxT( "invalid" ), wxT( "menu not found" ) );
  wxMenu* menu = node->GetData();
  return wxConvertFromGTKToWXLabel( menu->GetTitle() );
}

void wxMenuBar::SetLabelTop( size_t pos, const wxString& label ) {
  wxMenuList::compatibility_iterator node = m_menus.Item( pos );
  wxCHECK_RET( node, wxT( "menu not found" ) );
  wxMenu* menu = node->GetData();
  const wxString str( wxReplaceUnderscore( label ) );
  menu->SetTitle( str );
  if( menu->m_owner ) {
    gtk_label_set_text_with_mnemonic( GTK_LABEL( GTK_BIN( menu->m_owner )->child ), wxGTK_CONV( str ) );
  }
}

extern "C" {
  static void gtk_menu_clicked_callback( GtkWidget *widget, wxMenu *menu ) {
    if( g_isIdle ) {
      wxapp_install_idle_handler();
    }
    int id = menu->FindMenuIdByMenuItem( widget );
    /* should find it for normal (not popup) menu */
    wxASSERT_MSG( ( id != -1 ) || ( menu->GetInvokingWindow() != NULL ), _T( "在中找不到菜单项 gtk_menu_clicked_callback" ) );
    if( !menu->IsEnabled( id ) ) {
      return;
    }
    wxMenuItem* item = menu->FindChildItem( id );
    wxCHECK_RET( item, wxT( "菜单项回调出错" ) );
    if( item->GetId() == wxGTK_TITLE_ID ) {
      return;
    }
    if( item->IsCheckable() ) {
      bool isReallyChecked = item->IsChecked(),
           isInternallyChecked = item->wxMenuItemBase::IsChecked();
      item->wxMenuItemBase::Check( isReallyChecked );
      if( ( item->GetKind() == wxITEM_RADIO && !isReallyChecked ) ||
          ( isInternallyChecked == isReallyChecked ) ) {
        return;
      }
    }
    wxFrame* frame = NULL;
    if( menu->IsAttached() ) {
      frame = menu->GetMenuBar()->GetFrame();
    }
    if( frame ) {
      wxCommandEvent commandEvent( wxEVT_COMMAND_MENU_SELECTED, id );
      commandEvent.SetEventObject( frame );
      if( item->IsCheckable() ) {
        commandEvent.SetInt( item->IsChecked() );
      }
      commandEvent.SetEventObject( menu );
      frame->GetEventHandler()->ProcessEvent( commandEvent );
    } else {
      menu->SendEvent( id, item->IsCheckable() ? item->IsChecked() : -1 );
    }
  }
}

extern "C" {
  static void gtk_menu_hilight_callback( GtkWidget *widget, wxMenu *menu ) {
    if( g_isIdle ) {
      wxapp_install_idle_handler();
    }
    int id = menu->FindMenuIdByMenuItem( widget );
    wxASSERT( id != -1 );
    if( !menu->IsEnabled( id ) ) {
      return;
    }
    wxMenuEvent event( wxEVT_MENU_HIGHLIGHT, id );
    event.SetEventObject( menu );
    wxEvtHandler* handler = menu->GetEventHandler();
    if( handler && handler->ProcessEvent( event ) ) {
      return;
    }
    wxWindow *win = menu->GetInvokingWindow();
    if( win ) {
      win->GetEventHandler()->ProcessEvent( event );
    }
  }
}

extern "C" {
  static void gtk_menu_nolight_callback( GtkWidget *widget, wxMenu *menu ) {
    if( g_isIdle ) {
      wxapp_install_idle_handler();
    }
    int id = menu->FindMenuIdByMenuItem( widget );
    wxASSERT( id != -1 );
    if( !menu->IsEnabled( id ) ) {
      return;
    }
    wxMenuEvent event( wxEVT_MENU_HIGHLIGHT, -1 );
    event.SetEventObject( menu );
    wxEvtHandler* handler = menu->GetEventHandler();
    if( handler && handler->ProcessEvent( event ) ) {
      return;
    }
    wxWindow *win = menu->GetInvokingWindow();
    if( win ) {
      win->GetEventHandler()->ProcessEvent( event );
    }
  }
}

IMPLEMENT_DYNAMIC_CLASS( wxMenuItem, wxObject )

wxMenuItem *wxMenuItemBase::New( wxMenu *parentMenu, int id, const wxString& name, const wxString& help, wxItemKind kind, wxMenu *subMenu ) {
  return new wxMenuItem( parentMenu, id, name, help, kind, subMenu );
}

wxMenuItem::wxMenuItem( wxMenu *parentMenu, int id, const wxString& text, const wxString& help, wxItemKind kind, wxMenu *subMenu )
  : wxMenuItemBase( parentMenu, id, text, help, kind, subMenu ) {
  Init( text );
}

wxMenuItem::wxMenuItem( wxMenu *parentMenu, int id, const wxString& text, const wxString& help, bool isCheckable, wxMenu *subMenu )
  : wxMenuItemBase( parentMenu, id, text, help, isCheckable ? wxITEM_CHECK : wxITEM_NORMAL, subMenu ) {
  Init( text );
}

void wxMenuItem::Init( const wxString& text ) {
  m_labelWidget = ( GtkWidget * ) NULL;
  m_menuItem = ( GtkWidget * ) NULL;
  DoSetText( text );
}

wxMenuItem::~wxMenuItem() {
}

wxString wxMenuItemBase::GetLabelFromText( const wxString& text ) {
  wxString label;
  for( const wxChar *pc = text.c_str(); *pc; pc++ ) {
    if( *pc == wxT( '\t' ) ) {
      break;
    }
    if( *pc == wxT( '_' ) ) {
      pc++;
      label += *pc;
      continue;
    }
    if( *pc == wxT( '\\' ) ) {
      pc++;
      label += *pc;
      continue;
    }
    if( ( *pc == wxT( '&' ) ) && ( *( pc + 1 ) != wxT( '&' ) ) ) {
      continue;
    }
    label += *pc;
  }
  return label;
}

void wxMenuItem::SetText( const wxString& str ) {
  bool isstock = wxIsStockID( GetId() );
  const char *stockid = NULL;
  if( isstock ) {
    stockid = wxGetStockGtkID( GetId() );
  }
  wxString oldLabel = m_text;
  oldLabel = wxStripMenuCodes( oldLabel );
  oldLabel.Replace( wxT( "_" ), wxT( "" ) );
  wxString label1 = wxStripMenuCodes( str );
  wxString oldhotkey = GetHotKey();
  wxCharBuffer oldbuf = wxGTK_CONV_SYS( GetGtkHotKey( *this ) );
  DoSetText( str );
  if( oldLabel == label1 && oldhotkey == GetHotKey() ) {
    return;
  }
  if( m_menuItem ) {
    GtkLabel *label;
    if( m_labelWidget ) {
      label = ( GtkLabel* ) m_labelWidget;
    } else {
      label = GTK_LABEL( GTK_BIN( m_menuItem )->child );
    }
    wxString text = m_text;
    if( text.IsEmpty() && !IsSeparator() ) {
      wxASSERT_MSG( isstock, wxT( "A non-stock menu item with an empty label?" ) );
      text = wxGetStockLabel( GetId() );
      text = GTKProcessMenuItemLabel( text, NULL );
    }
    gtk_label_set_text_with_mnemonic( GTK_LABEL( label ), wxGTK_CONV_SYS( text ) );
  }
  guint accel_key;
  GdkModifierType accel_mods;
  if( oldbuf[( size_t )0] != '\0' ) {
    gtk_accelerator_parse( ( const char* ) oldbuf, &accel_key, &accel_mods );
    if( accel_key != 0 ) {
      gtk_widget_remove_accelerator( GTK_WIDGET( m_menuItem ), m_parentMenu->m_accel, accel_key, accel_mods );
    }
  } else if( isstock ) {
    if( wxGetStockGtkAccelerator( stockid, &accel_mods, &accel_key ) ) {
      gtk_widget_remove_accelerator( GTK_WIDGET( m_menuItem ), m_parentMenu->m_accel, accel_key, accel_mods );
    }
  }
  wxCharBuffer buf = wxGTK_CONV_SYS( GetGtkHotKey( *this ) );
  if( buf[( size_t )0] != '\0' ) {
    gtk_accelerator_parse( ( const char* ) buf, &accel_key, &accel_mods );
    if( accel_key != 0 ) {
      gtk_widget_add_accelerator( GTK_WIDGET( m_menuItem ), "activate", m_parentMenu->m_accel, accel_key,
                                  accel_mods, GTK_ACCEL_VISIBLE );
    }
  } else if( isstock ) {
    if( wxGetStockGtkAccelerator( stockid, &accel_mods, &accel_key ) )
      gtk_widget_add_accelerator( GTK_WIDGET( m_menuItem ), "activate", m_parentMenu->m_accel, accel_key,
                                  accel_mods, GTK_ACCEL_VISIBLE );
  }
}

wxString wxMenuItem::GTKProcessMenuItemLabel( const wxString& str, wxString *hotKey ) {
  wxString text;
  const wxChar *pc = str;
  while( ( *pc != wxT( '\0' ) ) && ( *pc != wxT( '\t' ) ) ) {
    if( ( *pc == wxT( '&' ) ) && ( *( pc + 1 ) == wxT( '&' ) ) ) {
      ++pc;
      text << wxT( '&' );
    } else if( *pc == wxT( '&' ) ) {
      text << wxT( '_' );
    } else if( *pc == wxT( '_' ) ) {
      text << wxT( "__" );
    } else {
      text << *pc;
    }
    ++pc;
  }
  if( hotKey ) {
    hotKey->Empty();
    if( *pc == wxT( '\t' ) ) {
      pc++;
      *hotKey = pc;
    }
  }
  return text;
}

void wxMenuItem::DoSetText( const wxString& str ) {
  m_text.Empty();
  m_text = GTKProcessMenuItemLabel( str, &m_hotKey );
}

wxAcceleratorEntry *wxMenuItem::GetAccel() const {
  if( !GetHotKey() ) {
    return NULL;
  }
  wxString label;
  label << wxT( '\t' ) << GetHotKey();
  return wxAcceleratorEntry::Create( label );
}

void wxMenuItem::Check( bool check ) {
  wxCHECK_RET( m_menuItem, wxT( "invalid menu item" ) );
  if( check == m_isChecked ) {
    return;
  }
  wxMenuItemBase::Check( check );
  switch( GetKind() ) {
    case wxITEM_CHECK:
    case wxITEM_RADIO:
      gtk_check_menu_item_set_active( ( GtkCheckMenuItem* )m_menuItem, ( gint )check );
      break;
    default:
      wxFAIL_MSG( _T( "can't check this item" ) );
  }
}

void wxMenuItem::Enable( bool enable ) {
  wxCHECK_RET( m_menuItem, wxT( "invalid menu item" ) );
  gtk_widget_set_sensitive( m_menuItem, enable );
  wxMenuItemBase::Enable( enable );
}

bool wxMenuItem::IsChecked() const {
  wxCHECK_MSG( m_menuItem, false, wxT( "invalid menu item" ) );
  wxCHECK_MSG( IsCheckable(), false,
               wxT( "can't get state of uncheckable item!" ) );
  return ( ( GtkCheckMenuItem* )m_menuItem )->active != 0;
}

wxString wxMenuItem::GetItemLabel() const {
  wxString label = wxConvertFromGTKToWXLabel( m_text );
  if( !m_hotKey.IsEmpty() ) {
    label = label + wxT( "\t" ) + m_hotKey;
  }
  return label;
}

extern "C" {
  static gboolean can_activate_accel( GtkWidget*, guint, wxMenu* menu ) {
    menu->UpdateUI();
    return true;
  }
}

IMPLEMENT_DYNAMIC_CLASS( wxMenu, wxEvtHandler )

void wxMenu::Init() {
  m_accel = gtk_accel_group_new();
  m_menu = gtk_menu_new();
  g_object_ref( m_menu );
  gtk_object_sink( GTK_OBJECT( m_menu ) );
  m_owner = ( GtkWidget* ) NULL;
  if( m_style & wxMENU_TEAROFF ) {
    GtkWidget *tearoff = gtk_tearoff_menu_item_new();
    gtk_menu_shell_append( GTK_MENU_SHELL( m_menu ), tearoff );
  }
  m_prevRadio = NULL;
  if( !m_title.empty() ) {
    Append( wxGTK_TITLE_ID, m_title );
  }
}

wxMenu::~wxMenu() {
  if( GTK_IS_WIDGET( m_menu ) ) {
    g_object_unref( m_menu );
    g_object_unref( m_accel );
    if( m_owner ) {
      gtk_widget_destroy( m_menu );
    }
  }
}

void wxMenu::SetLayoutDirection( const wxLayoutDirection dir ) {
  if( m_owner ) {
    wxWindow::GTKSetLayout( m_owner, dir );
  }
}

wxLayoutDirection wxMenu::GetLayoutDirection() const {
  return wxWindow::GTKGetLayout( m_owner );
}

bool wxMenu::GtkAppend( wxMenuItem *mitem, int pos ) {
  GtkWidget *menuItem;
  wxString text = mitem->GetText();
  int id = mitem->GetId();
  bool isstock = wxIsStockID( id );
  const char *stockid = NULL;
  if( isstock ) {
    stockid = wxGetStockGtkID( mitem->GetId() );
  }
  if( text.IsEmpty() && !mitem->IsSeparator() ) {
    wxASSERT_MSG( isstock, wxT( "A non-stock menu item with an empty label?" ) );
    text = wxGetStockLabel( id );
    text = wxMenuItem::GTKProcessMenuItemLabel( text, NULL );
  }
  if( mitem->IsSeparator() ) {
    menuItem = gtk_separator_menu_item_new();
  } else if( mitem->GetBitmap().Ok() ||
             ( mitem->GetKind() == wxITEM_NORMAL && isstock ) ) {
    wxBitmap bitmap( mitem->GetBitmap() );
    menuItem = gtk_image_menu_item_new_with_mnemonic( wxGTK_CONV_SYS( text ) );
    GtkWidget *image;
    if( !bitmap.Ok() ) {
      image = stockid ? gtk_image_new_from_stock( stockid, GTK_ICON_SIZE_MENU )
              : NULL;
    } else {
      wxASSERT_MSG( mitem->GetKind() == wxITEM_NORMAL, _T( "only normal menu items can have bitmaps" ) );
      image = gtk_image_new_from_pixbuf( bitmap.GetPixbuf() );
    }
    if( image ) {
      gtk_widget_show( image );
      gtk_image_menu_item_set_image( GTK_IMAGE_MENU_ITEM( menuItem ), image );
    }
    m_prevRadio = NULL;
  } else {
    switch( mitem->GetKind() ) {
      case wxITEM_CHECK: {
        menuItem = gtk_check_menu_item_new_with_mnemonic( wxGTK_CONV_SYS( text ) );
        m_prevRadio = NULL;
        break;
      }
      case wxITEM_RADIO: {
        GSList *group = NULL;
        if( m_prevRadio == NULL ) {
          m_prevRadio = menuItem =
                          gtk_radio_menu_item_new_with_mnemonic( group, wxGTK_CONV_SYS( text ) );
        } else {
          group = gtk_radio_menu_item_get_group( GTK_RADIO_MENU_ITEM( m_prevRadio ) );
          m_prevRadio = menuItem =
                          gtk_radio_menu_item_new_with_mnemonic( group, wxGTK_CONV_SYS( text ) );
        }
        break;
      }
      default:
        wxFAIL_MSG( _T( "unexpected menu item kind" ) );
      case wxITEM_NORMAL: {
        menuItem = gtk_menu_item_new_with_mnemonic( wxGTK_CONV_SYS( text ) );
        m_prevRadio = NULL;
        break;
      }
    }
  }
  guint accel_key;
  GdkModifierType accel_mods;
  wxCharBuffer buf = wxGTK_CONV_SYS( GetGtkHotKey( *mitem ) );
  if( buf[( size_t )0] != '\0' ) {
    gtk_accelerator_parse( ( const char* ) buf, &accel_key, &accel_mods );
    if( accel_key != 0 ) {
      gtk_widget_add_accelerator( GTK_WIDGET( menuItem ), "activate", m_accel, accel_key, accel_mods, GTK_ACCEL_VISIBLE );
    }
  } else if( isstock ) {
    if( wxGetStockGtkAccelerator( stockid, &accel_mods, &accel_key ) ) {
      gtk_widget_add_accelerator( GTK_WIDGET( menuItem ), "activate", m_accel, accel_key, accel_mods, GTK_ACCEL_VISIBLE );
    }
  }
  if( pos == -1 ) {
    gtk_menu_shell_append( GTK_MENU_SHELL( m_menu ), menuItem );
  } else
  { gtk_menu_shell_insert( GTK_MENU_SHELL( m_menu ), menuItem, pos ); }
  gtk_widget_show( menuItem );
  if( !mitem->IsSeparator() ) {
    wxASSERT_MSG( menuItem, wxT( "invalid menuitem" ) );
    g_signal_connect( menuItem, "select",G_CALLBACK( gtk_menu_hilight_callback ), this );
    g_signal_connect( menuItem, "deselect",G_CALLBACK( gtk_menu_nolight_callback ), this );
    if( mitem->IsSubMenu() && mitem->GetKind() != wxITEM_RADIO && mitem->GetKind() != wxITEM_CHECK ) {
      gtk_menu_item_set_submenu( GTK_MENU_ITEM( menuItem ), mitem->GetSubMenu()->m_menu );
      gtk_widget_show( mitem->GetSubMenu()->m_menu );
      if( m_invokingWindow ) {
        wxMenubarSetInvokingWindow( mitem->GetSubMenu(), m_invokingWindow );
      }
    } else {
      if( gtk_check_version( 2, 4, 0 ) == NULL ) {
        g_signal_connect( menuItem, "can_activate_accel", G_CALLBACK( can_activate_accel ), this );
      }
      g_signal_connect( menuItem, "activate", G_CALLBACK( gtk_menu_clicked_callback ), this );
    }
  }
  mitem->SetMenuItem( menuItem );
  return true;
}

wxMenuItem* wxMenu::DoAppend( wxMenuItem *mitem ) {
  if( !GtkAppend( mitem ) ) {
    return NULL;
  }
  return wxMenuBase::DoAppend( mitem );
}

wxMenuItem* wxMenu::DoInsert( size_t pos, wxMenuItem *item ) {
  if( !wxMenuBase::DoInsert( pos, item ) ) {
    return NULL;
  }
  if( !GtkAppend( item, ( int )pos ) ) {
    return NULL;
  }
  return item;
}

wxMenuItem *wxMenu::DoRemove( wxMenuItem *item ) {
  if( !wxMenuBase::DoRemove( item ) ) {
    return ( wxMenuItem * )NULL;
  }
  GtkWidget* mitem = item->GetMenuItem();
  gtk_menu_item_set_submenu( GTK_MENU_ITEM( mitem ), NULL );
  gtk_widget_destroy( mitem );
  item->SetMenuItem( NULL );
  return item;
}

int wxMenu::FindMenuIdByMenuItem( GtkWidget *menuItem ) const {
  wxMenuItemList::compatibility_iterator node = m_items.GetFirst();
  while( node ) {
    wxMenuItem *item = node->GetData();
    if( item->GetMenuItem() == menuItem ) {
      return item->GetId();
    }
    node = node->GetNext();
  }
  return wxNOT_FOUND;
}

void wxMenu::Attach( wxMenuBarBase *menubar ) {
  wxMenuBase::Attach( menubar );
  SetLayoutDirection( menubar->GetLayoutDirection() );
}

static wxString GetGtkHotKey( const wxMenuItem& item ) {
  wxString hotkey;
  wxAcceleratorEntry *accel = item.GetAccel();
  if( accel ) {
    int flags = accel->GetFlags();
    if( flags & wxACCEL_ALT ) {
      hotkey += wxT( "<alt>" );
    }
    if( flags & wxACCEL_CTRL ) {
      hotkey += wxT( "<control>" );
    }
    if( flags & wxACCEL_SHIFT ) {
      hotkey += wxT( "<shift>" );
    }
    int code = accel->GetKeyCode();
    switch( code ) {
      case WXK_F1:
      case WXK_F2:
      case WXK_F3:
      case WXK_F4:
      case WXK_F5:
      case WXK_F6:
      case WXK_F7:
      case WXK_F8:
      case WXK_F9:
      case WXK_F10:
      case WXK_F11:
      case WXK_F12:
      case WXK_F13:
      case WXK_F14:
      case WXK_F15:
      case WXK_F16:
      case WXK_F17:
      case WXK_F18:
      case WXK_F19:
      case WXK_F20:
      case WXK_F21:
      case WXK_F22:
      case WXK_F23:
      case WXK_F24:
        hotkey += wxString::Format( wxT( "F%d" ), code - WXK_F1 + 1 );
        break;
      case WXK_INSERT:
        hotkey << wxT( "Insert" );
        break;
      case WXK_DELETE:
        hotkey << wxT( "Delete" );
        break;
      case WXK_UP:
        hotkey << wxT( "Up" );
        break;
      case WXK_DOWN:
        hotkey << wxT( "Down" );
        break;
      case WXK_PAGEUP:
        hotkey << wxT( "Page_Up" );
        break;
      case WXK_PAGEDOWN:
        hotkey << wxT( "Page_Down" );
        break;
      case WXK_LEFT:
        hotkey << wxT( "Left" );
        break;
      case WXK_RIGHT:
        hotkey << wxT( "Right" );
        break;
      case WXK_HOME:
        hotkey << wxT( "Home" );
        break;
      case WXK_END:
        hotkey << wxT( "End" );
        break;
      case WXK_RETURN:
        hotkey << wxT( "Return" );
        break;
      case WXK_BACK:
        hotkey << wxT( "BackSpace" );
        break;
      case WXK_TAB:
        hotkey << wxT( "Tab" );
        break;
      case WXK_ESCAPE:
        hotkey << wxT( "Esc" );
        break;
      case WXK_SPACE:
        hotkey << wxT( "space" );
        break;
      case WXK_MULTIPLY:
        hotkey << wxT( "Multiply" );
        break;
      case WXK_ADD:
        hotkey << wxT( "Add" );
        break;
      case WXK_SEPARATOR:
        hotkey << wxT( "Separator" );
        break;
      case WXK_SUBTRACT:
        hotkey << wxT( "Subtract" );
        break;
      case WXK_DECIMAL:
        hotkey << wxT( "Decimal" );
        break;
      case WXK_DIVIDE:
        hotkey << wxT( "Divide" );
        break;
      case WXK_CANCEL:
        hotkey << wxT( "Cancel" );
        break;
      case WXK_CLEAR:
        hotkey << wxT( "Clear" );
        break;
      case WXK_MENU:
        hotkey << wxT( "Menu" );
        break;
      case WXK_PAUSE:
        hotkey << wxT( "Pause" );
        break;
      case WXK_CAPITAL:
        hotkey << wxT( "Capital" );
        break;
      case WXK_SELECT:
        hotkey << wxT( "Select" );
        break;
      case WXK_PRINT:
        hotkey << wxT( "Print" );
        break;
      case WXK_EXECUTE:
        hotkey << wxT( "Execute" );
        break;
      case WXK_SNAPSHOT:
        hotkey << wxT( "Snapshot" );
        break;
      case WXK_HELP:
        hotkey << wxT( "Help" );
        break;
      case WXK_NUMLOCK:
        hotkey << wxT( "Num_Lock" );
        break;
      case WXK_SCROLL:
        hotkey << wxT( "Scroll_Lock" );
        break;
      case WXK_NUMPAD_INSERT:
        hotkey << wxT( "KP_Insert" );
        break;
      case WXK_NUMPAD_DELETE:
        hotkey << wxT( "KP_Delete" );
        break;
      case WXK_NUMPAD_SPACE:
        hotkey << wxT( "KP_Space" );
        break;
      case WXK_NUMPAD_TAB:
        hotkey << wxT( "KP_Tab" );
        break;
      case WXK_NUMPAD_ENTER:
        hotkey << wxT( "KP_Enter" );
        break;
      case WXK_NUMPAD_F1:
      case WXK_NUMPAD_F2:
      case WXK_NUMPAD_F3:
      case WXK_NUMPAD_F4:
        hotkey += wxString::Format( wxT( "KP_F%d" ), code - WXK_NUMPAD_F1 + 1 );
        break;
      case WXK_NUMPAD_HOME:
        hotkey << wxT( "KP_Home" );
        break;
      case WXK_NUMPAD_LEFT:
        hotkey << wxT( "KP_Left" );
        break;
      case WXK_NUMPAD_UP:
        hotkey << wxT( "KP_Up" );
        break;
      case WXK_NUMPAD_RIGHT:
        hotkey << wxT( "KP_Right" );
        break;
      case WXK_NUMPAD_DOWN:
        hotkey << wxT( "KP_Down" );
        break;
      case WXK_NUMPAD_PAGEUP:
        hotkey << wxT( "KP_Page_Up" );
        break;
      case WXK_NUMPAD_PAGEDOWN:
        hotkey << wxT( "KP_Page_Down" );
        break;
      case WXK_NUMPAD_END:
        hotkey << wxT( "KP_End" );
        break;
      case WXK_NUMPAD_BEGIN:
        hotkey << wxT( "KP_Begin" );
        break;
      case WXK_NUMPAD_EQUAL:
        hotkey << wxT( "KP_Equal" );
        break;
      case WXK_NUMPAD_MULTIPLY:
        hotkey << wxT( "KP_Multiply" );
        break;
      case WXK_NUMPAD_ADD:
        hotkey << wxT( "KP_Add" );
        break;
      case WXK_NUMPAD_SEPARATOR:
        hotkey << wxT( "KP_Separator" );
        break;
      case WXK_NUMPAD_SUBTRACT:
        hotkey << wxT( "KP_Subtract" );
        break;
      case WXK_NUMPAD_DECIMAL:
        hotkey << wxT( "KP_Decimal" );
        break;
      case WXK_NUMPAD_DIVIDE:
        hotkey << wxT( "KP_Divide" );
        break;
      case WXK_NUMPAD0:
      case WXK_NUMPAD1:
      case WXK_NUMPAD2:
      case WXK_NUMPAD3:
      case WXK_NUMPAD4:
      case WXK_NUMPAD5:
      case WXK_NUMPAD6:
      case WXK_NUMPAD7:
      case WXK_NUMPAD8:
      case WXK_NUMPAD9:
        hotkey += wxString::Format( wxT( "KP_%d" ), code - WXK_NUMPAD0 );
        break;
      case WXK_WINDOWS_LEFT:
        hotkey << wxT( "Super_L" );
        break;
      case WXK_WINDOWS_RIGHT:
        hotkey << wxT( "Super_R" );
        break;
      case WXK_WINDOWS_MENU:
        hotkey << wxT( "Menu" );
        break;
      case WXK_COMMAND:
        hotkey << wxT( "Command" );
        break;
      default:
        if( code < 127 ) {
          wxString name = wxGTK_CONV_BACK( gdk_keyval_name( ( guint )code ) );
          if( name ) {
            hotkey << name;
            break;
          }
        }
        wxFAIL_MSG( wxT( "unknown keyboard accel" ) );
    }
    delete accel;
  }
  return hotkey;
}

#if wxUSE_MENUS_NATIVE

extern "C" void gtk_pop_hide_callback( GtkWidget * ( widget ), bool* is_waiting ) {
  *is_waiting = false;
}

void SetInvokingWindow( wxMenu *menu, wxWindow* win ) {
  menu->SetInvokingWindow( win );
  wxMenuItemList::compatibility_iterator node = menu->GetMenuItems().GetFirst();
  while( node ) {
    wxMenuItem *menuitem = node->GetData();
    if( menuitem->IsSubMenu() ) {
      SetInvokingWindow( menuitem->GetSubMenu(), win );
    }
    node = node->GetNext();
  }
}

extern "C" void wxPopupMenuPositionCallback( GtkMenu *menu, gint *x, gint *y, gboolean * ( whatever ), gpointer user_data ) {
  GtkRequisition req;
  gtk_widget_get_child_requisition( GTK_WIDGET( menu ), &req );
  wxSize sizeScreen = wxGetDisplaySize();
  wxPoint *pos = ( wxPoint* )user_data;
  gint xmax = sizeScreen.x - req.width,
       ymax = sizeScreen.y - req.height;
  *x = pos->x < xmax ? pos->x : xmax;
  *y = pos->y < ymax ? pos->y : ymax;
}

bool wxWindow::DoPopupMenu( wxMenu *menu, int x, int y ) {
  wxCHECK_MSG( m_widget != NULL, false, wxT( "invalid window" ) );
  wxCHECK_MSG( menu != NULL, false, wxT( "invalid popup-menu" ) );
  SetInvokingWindow( menu, this );
  menu->UpdateUI();
  bool is_waiting = true;
  gulong handler = g_signal_connect( menu->m_menu, "hide", G_CALLBACK( gtk_pop_hide_callback ), &is_waiting );
  wxPoint pos;
  gpointer userdata;
  GtkMenuPositionFunc posfunc;
  if( x == -1 && y == -1 ) {
    userdata = NULL;
    posfunc = NULL;
  } else {
    pos = ClientToScreen( wxPoint( x, y ) );
    userdata = &pos;
    posfunc = wxPopupMenuPositionCallback;
  }
  wxMenuEvent eventOpen( wxEVT_MENU_OPEN, -1, menu );
  DoCommonMenuCallbackCode( menu, eventOpen );
  gtk_menu_popup(
    GTK_MENU( menu->m_menu ),
    ( GtkWidget * ) NULL,
    ( GtkWidget * ) NULL,
    posfunc,
    userdata,
    0,
    gtk_get_current_event_time()
  );
  while( is_waiting ) {
    gtk_main_iteration();
  }
  g_signal_handler_disconnect( menu->m_menu, handler );
  wxMenuEvent eventClose( wxEVT_MENU_CLOSE, -1, menu );
  DoCommonMenuCallbackCode( menu, eventClose );
  return true;
}

#endif

#include <gtk/gtk.h>

#define STOCKITEM(wx,gtk)   \
  case wx:   \
  return gtk;
#define STOCKITEM_MISSING(wx)   \
  case wx:   \
  return NULL;

const char *wxGetStockGtkID( wxWindowID id ) {
  switch( id ) {
      STOCKITEM( wxID_ABOUT, GTK_STOCK_ABOUT )
      STOCKITEM( wxID_ADD, GTK_STOCK_ADD )
      STOCKITEM( wxID_APPLY, GTK_STOCK_APPLY )
      STOCKITEM( wxID_BOLD, GTK_STOCK_BOLD )
      STOCKITEM( wxID_CANCEL, GTK_STOCK_CANCEL )
      STOCKITEM( wxID_CLEAR, GTK_STOCK_CLEAR )
      STOCKITEM( wxID_CLOSE, GTK_STOCK_CLOSE )
      STOCKITEM( wxID_COPY, GTK_STOCK_COPY )
      STOCKITEM( wxID_CUT, GTK_STOCK_CUT )
      STOCKITEM( wxID_DELETE, GTK_STOCK_DELETE )
      STOCKITEM( wxID_EDIT, GTK_STOCK_EDIT )
      STOCKITEM( wxID_FIND, GTK_STOCK_FIND )
      STOCKITEM( wxID_FILE, GTK_STOCK_FILE )
      STOCKITEM( wxID_REPLACE, GTK_STOCK_FIND_AND_REPLACE )
      STOCKITEM( wxID_BACKWARD, GTK_STOCK_GO_BACK )
      STOCKITEM( wxID_DOWN, GTK_STOCK_GO_DOWN )
      STOCKITEM( wxID_FORWARD, GTK_STOCK_GO_FORWARD )
      STOCKITEM( wxID_UP, GTK_STOCK_GO_UP )
      STOCKITEM( wxID_HELP, GTK_STOCK_HELP )
      STOCKITEM( wxID_HOME, GTK_STOCK_HOME )
      STOCKITEM( wxID_INDENT, GTK_STOCK_INDENT )
      STOCKITEM( wxID_INDEX, GTK_STOCK_INDEX )
      STOCKITEM( wxID_ITALIC, GTK_STOCK_ITALIC )
      STOCKITEM( wxID_JUSTIFY_CENTER, GTK_STOCK_JUSTIFY_CENTER )
      STOCKITEM( wxID_JUSTIFY_FILL, GTK_STOCK_JUSTIFY_FILL )
      STOCKITEM( wxID_JUSTIFY_LEFT, GTK_STOCK_JUSTIFY_LEFT )
      STOCKITEM( wxID_JUSTIFY_RIGHT, GTK_STOCK_JUSTIFY_RIGHT )
      STOCKITEM( wxID_NEW, GTK_STOCK_NEW )
      STOCKITEM( wxID_NO, GTK_STOCK_NO )
      STOCKITEM( wxID_OK, GTK_STOCK_OK )
      STOCKITEM( wxID_OPEN, GTK_STOCK_OPEN )
      STOCKITEM( wxID_PASTE, GTK_STOCK_PASTE )
      STOCKITEM( wxID_PREFERENCES, GTK_STOCK_PREFERENCES )
      STOCKITEM( wxID_PRINT, GTK_STOCK_PRINT )
      STOCKITEM( wxID_PREVIEW, GTK_STOCK_PRINT_PREVIEW )
      STOCKITEM( wxID_PROPERTIES, GTK_STOCK_PROPERTIES )
      STOCKITEM( wxID_EXIT, GTK_STOCK_QUIT )
      STOCKITEM( wxID_REDO, GTK_STOCK_REDO )
      STOCKITEM( wxID_REFRESH, GTK_STOCK_REFRESH )
      STOCKITEM( wxID_REMOVE, GTK_STOCK_REMOVE )
      STOCKITEM( wxID_REVERT_TO_SAVED, GTK_STOCK_REVERT_TO_SAVED )
      STOCKITEM( wxID_SAVE, GTK_STOCK_SAVE )
      STOCKITEM( wxID_SAVEAS, GTK_STOCK_SAVE_AS )
      STOCKITEM( wxID_SELECTALL, GTK_STOCK_SELECT_ALL )
      STOCKITEM( wxID_STOP, GTK_STOCK_STOP )
      STOCKITEM( wxID_UNDELETE, GTK_STOCK_UNDELETE )
      STOCKITEM( wxID_UNDERLINE, GTK_STOCK_UNDERLINE )
      STOCKITEM( wxID_UNDO, GTK_STOCK_UNDO )
      STOCKITEM( wxID_UNINDENT, GTK_STOCK_UNINDENT )
      STOCKITEM( wxID_YES, GTK_STOCK_YES )
      STOCKITEM( wxID_ZOOM_100, GTK_STOCK_ZOOM_100 )
      STOCKITEM( wxID_ZOOM_FIT, GTK_STOCK_ZOOM_FIT )
      STOCKITEM( wxID_ZOOM_IN, GTK_STOCK_ZOOM_IN )
      STOCKITEM( wxID_ZOOM_OUT, GTK_STOCK_ZOOM_OUT )
    default:
      wxFAIL_MSG( _T( "invalid stock item ID" ) );
      break;
  };
  return NULL;
}

#undef STOCKITEM

bool wxGetStockGtkAccelerator( const char *id, GdkModifierType *mod, guint *key ) {
  if( !id ) {
    return false;
  }
  GtkStockItem stock_item;
  if( gtk_stock_lookup( id, &stock_item ) ) {
    if( key ) {
      *key = stock_item.keyval;
    }
    if( mod ) {
      *mod = stock_item.modifier;
    }
    if( stock_item.keyval ) {
      return true;
    }
  }
  return false;
}
